package pl.edu.pw.elka.tinyrepo.common;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * Definiuje jednolity mechanizm wprowadzania argumentow
 * z linii komend.
 * 
 * Dla zestawu aplikacji tinyrepo argumenty z linii komend wystepuja
 * w nastepujacym porzadku:
 * argumenty = -litera_argumentu wartosc [argumenty] 
 * 
 * @author Piotr Jarosik
 */
public abstract class ArgumentLineParser {
   /** Wzorzec dla pojedynczego paramatru. */
   private final Pattern charArgPattern = Pattern.compile("^-([a-zA-Z])$");
   /** Mapa znakow argumentow na handlery ustawiajace odpowiednia wartosc */
   private Map<Character, ArgumentHandler> handlerMap = 
      new HashMap<Character, ArgumentLineParser.ArgumentHandler>();
   
   /**
    * Konstruktor domyslny.
    * Tworzy pusty parser bez handlerow. 
    */
   public ArgumentLineParser() {
   }
   
   /**
    * Dodaje nowego handlera ktory bedzie obslugiwal okreslony znak.
    * 
    * @param c - znak
    * @param h - handler
    */
   protected void addHandler(final Character c, final ArgumentHandler h) {
      handlerMap.put(c, h);
   }
   
   /**
    * Wywoluje dla kazdego argumentu odpowiedniego handlera.
    * Moze wystepowac wiele razy ten sam argument, i wtedy
    * dla kazdego argumentu bedzie wykonywany okreslony handler.
    * Np. dla -t a -t b zostanie wywolany handler dla t dwa razy. </br>
    * Wielkosc liter argumentow ma znaczenie.
    * 
    * @param args - lista argumentow.
    * @throws UnsupportedOperationException - w przypadku gdy litera nie jest
    * obslugiwana lub wprowadzono po kresce cos innego niz litere
    */
   public void parse(final String[] args) {
      boolean isTimeForArgChar = true;
      Character arg; // wyluskiwany znak argumentu
      String value; // wartosc parametru
      ArgumentHandler h = null; // handler wartosci argumentu
      for(String s : args) {
         arg = null;
         if(isTimeForArgChar) { 
            Matcher matcher = charArgPattern.matcher(s);
            if(!matcher.find()) // niepoprawny argument
               throw new UnsupportedOperationException("ARG: Wrong argument: "+s);
            arg = matcher.group(1).charAt(0); // FIXME uelastycznic 
            if((h = handlerMap.get(arg)) == null) // argument nie obslugiwany
               throw new UnsupportedOperationException("ARG: Wrong argument: "+s);
            isTimeForArgChar = false;
         }
         else {
            h.handle(s);
            isTimeForArgChar = true;
         }
      }
      if(!isTimeForArgChar && args.length != 0) // zostal ostatni argument bez wartosci
         throw new RuntimeException("ARG: argument without value: "+args[args.length-1]);
   }
   
   abstract public void setPort(Integer port);
   
   /**
    * Interfejs handlerow do ustawiania poszczegolnych wartosci argumentow.
    * 
    * @author Piotr Jarosik
    */
   public interface ArgumentHandler {
      /**
       * Obsluga wartosci w postaci ciagu znakow.
       * 
       * @param value - wartosc w postaci ciagu znakow.
       */
      public void handle(final String value);
   }
   
   /**
    * Wczytuje port z linii komend. Dla obu serwerow jest wspolny.
    * 
    * @author Piotr Jarosik
    */
   public class PortArgumentHandler implements ArgumentHandler {
      @Override
      public void handle(final String value) {
         try {
            Integer v = Integer.decode(value);
            setPort(v);
         } catch (Exception e) {
            throw new RuntimeException("ARG: Wrong port arg number: "+value);
         }
      }
      
   }
}
